<template>
  <div class="upload-container">
    <div class="upload-area" @dragover.prevent="handleDragOver" @drop.prevent="handleFileDrop">
      <input type="file" multiple @change="handleFileSelect" ref="fileInput" class="file-input">
      <div class="upload-prompt">
        <span class="material-icons"></span>
        <p>点击选择文件或拖拽文件到此区域</p>
        <p class="hint">支持批量上传，单文件最大3GB</p>
      </div>
    </div>

    <transition-group name="file-list" tag="div" class="file-list">
      <div v-for="file in uploadQueue" :key="file.id" class="file-item" :class="{
        'status-uploading': file.status === STATUS.UPLOADING,
        'status-success': file.status === STATUS.SUCCESS,
        'status-error': file.status === STATUS.ERROR,
        'status-stalled': isConnectionLost(file)
      }">
        <div class="file-info">
          <span class="file-icon">
            <span class="material-icons">{{ getFileIcon(file.name) }}</span>
          </span>
          <div class="file-meta">
            <div class="filename">{{ file.name }}</div>
            <div class="file-size">{{ formatFileSize(file.size) }}</div>
            <!-- 新增错误信息展示区域 -->
            <div v-if="file.status === STATUS.ERROR" class="error-detail">
              {{ getErrorDetail(file) }}
            </div>
          </div>
          <div class="file-actions">
            <button v-if="file.status === STATUS.ERROR" @click="retryUpload(file)" class="retry-btn">
              <span class="material-icons">refresh</span>
              重试 ({{ MAX_FILE_RETRIES - file.retries }})
            </button>
            <button v-if="!['uploading', 'success'].includes(file.status)" @click="removeFile(file)" class="remove-btn">
              <span class="material-icons">close</span>
            </button>
          </div>
        </div>

        <div class="progress-container">
          <div class="progress-bar">
            <div class="progress-fill" :style="{ width: `${file.progress}%` }"></div>
          </div>
          <div class="progress-info">
            <span class="status-text">{{ getStatusText(file) }}</span>
            <span class="percentage">{{ file.progress }}%</span>
          </div>
        </div>
      </div>
    </transition-group>

    <div class="global-controls">
      <button class="start-btn" @click="startAllUploads" :disabled="!uploadQueue.length || isUploading">
        {{ uploadButtonText }}
      </button>
      <button class="clear-btn" @click="clearCompleted" :disabled="!hasCompletedFiles">
        清除已完成
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import CryptoJS from 'crypto-js'
import pLimit from 'p-limit'
import { getToken } from "@/utils/auth";
import { useRoute } from 'vue-router';
import { getArchive } from "@/api/manage/archive";
import { addDocument, fullTextOcr, doOcr } from "@/api/manage/document";

const MAX_RETRY = 3
// 最大文件级重试次数
const MAX_FILE_RETRIES = 2
// 根据CPU核心数设置并行度
const CONCURRENCY = Math.max(1, navigator.hardwareConcurrency - 1);

const file = ref(null)
const uploadStatus = ref('')
const uploadManager = ref(null)
const route = useRoute()
const archive = ref(null)
const isEncrypt = ref(false)
/* 上传请求头 */
const headers = ref({ Authorization: "Bearer " + getToken() });
// 获取上传档案的信息
const importArchiveId = route.query.archiveId
onMounted(async () => {
  getArchive(importArchiveId).then(response => {
    archive.value = response.data;
    if (archive.value.secretLevel === '3l') {
      isEncrypt.value = true
    }
  })
})

// 页面修改
const STATUS = {
  PENDING: 'pending',
  UPLOADING: 'uploading',
  SUCCESS: 'success',
  ERROR: 'error'
}
const fileInput = ref(null)
const uploadQueue = reactive([])
let nextFileId = 1

// 计算属性
const isUploading = computed(() =>
  uploadQueue.some(file => file.status === STATUS.UPLOADING)
)

const uploadButtonText = computed(() => {
  if (isUploading.value) return '上传中...'
  const pendingCount = uploadQueue.filter(f =>
    [STATUS.PENDING, STATUS.ERROR].includes(f.status)
  ).length
  return pendingCount ? `开始上传 (${pendingCount})` : '全部完成'
})

const hasCompletedFiles = computed(() =>
  uploadQueue.some(file => file.status === STATUS.SUCCESS)
)

// 文件处理方法
const handleFileSelect = (e) => {
  addFilesToQueue([...e.target.files])
  e.target.value = null // 清除input值允许重复选择
}

const handleFileDrop = (e) => {
  addFilesToQueue([...e.dataTransfer.files])
}

const addFilesToQueue = (files) => {
  files.forEach(file => {
    if (!uploadQueue.some(f => f.name === file.name && f.size === file.size)) {
      uploadQueue.push({
        id: nextFileId++,
        file,
        name: file.name,
        size: file.size,
        progress: 0,
        status: STATUS.PENDING,
        retries: 0,
        manager: null
      })
    }
  })
}

// 上传控制方法
const startAllUploads = async () => {
  for (const fileItem of uploadQueue) {
    // 跳过已完成文件
    if (fileItem.status === STATUS.SUCCESS) continue

    try {
      // 检查重试次数
      if (fileItem.retries >= MAX_FILE_RETRIES) {
        fileItem.status = STATUS.ERROR
        continue
      }

      // 执行上传
      await startSingleUpload(fileItem)
    } catch (error) {
      // 网络错误特殊处理
      fileItem.status = STATUS.ERROR
      fileItem.progress = 0
      continue // 继续下一个文件
    }
  }
}

// 在 startSingleUpload 方法内部添加
const startSingleUpload = async (fileItem) => {
  try {
    fileItem.status = STATUS.UPLOADING
    fileItem.manager = new UploadManager(fileItem.file)
    await fileItem.manager.initialize()

    // 进度回调（添加心跳检测）
    let lastProgressTime = Date.now()
    fileItem.manager.onProgress = (progress) => {
      fileItem.progress = progress
      lastProgressTime = Date.now() // 更新最后活动时间
    }

    // 添加超时监控（30秒无进展视为失败）
    const progressWatcher = setInterval(() => {
      if (Date.now() - lastProgressTime > 30000) {
        clearInterval(progressWatcher)
        throw new Error('上传超时')
      }
    }, 1000)

    // 执行上传
    const success = await fileItem.manager.startUpload()
    clearInterval(progressWatcher)

    if (success) {
      fileItem.status = STATUS.SUCCESS
      fileItem.progress = 100
    }
  } catch (error) {
    console.error(`[${fileItem.name}] 上传失败:`, error)
    fileItem.retries++
    fileItem.status = fileItem.retries < MAX_FILE_RETRIES
      ? STATUS.PENDING
      : STATUS.ERROR
    throw error // 向上传播错误
  }
}

const retryUpload = async (fileItem) => {
  fileItem.status = STATUS.PENDING
  fileItem.progress = 0
  await startSingleUpload(fileItem)
}

// UI辅助方法
const getStatusText = (file) => {
  switch (file.status) {
    case STATUS.UPLOADING: return '上传中...'
    case STATUS.SUCCESS: return '上传成功'
    case STATUS.ERROR: return `上传失败 (已重试 ${file.retries} 次)`
    default: return '等待上传'
  }
}

const formatFileSize = (bytes) => {
  if (bytes === 0) return '0 Bytes'
  const k = 1024
  const sizes = ['Bytes', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const getFileIcon = (filename) => {
  const ext = filename.split('.').pop().toLowerCase()
  const icons = {
    pdf: 'pdf文档',
    mp4: 'mp4视频',
    mp3: 'mp3音频',
    png: 'png图片',
    jpg: 'jpg图片',
    jpeg: 'jpeg图片',
    gif: 'gif图片',
    doc: 'doc文档',
    docx: 'docx文档',
    xls: 'xls文档',
    xlsx: 'xlsx文档',
    ppt: 'ppt文档',
    pptx: 'pptx文档',
    txt: 'txt文档',
    default: '未知文件'
  }
  return icons[ext] || icons.default
}

// 清理方法
const removeFile = (fileItem) => {
  const index = uploadQueue.findIndex(f => f.id === fileItem.id)
  if (index !== -1) {
    uploadQueue.splice(index, 1)
  }
}

const clearCompleted = () => {
  for (let i = uploadQueue.length - 1; i >= 0; i--) {
    if (uploadQueue[i].status === STATUS.SUCCESS) {
      uploadQueue.splice(i, 1)
    }
  }
}

// 更新状态样式
const statusClass = computed(() => ({
  'status-uploading': fileItem.status === STATUS.UPLOADING,
  'status-error': fileItem.status === STATUS.ERROR,
  'status-stalled': isConnectionLost(fileItem) // 新增停滞状态
}))

// 检测网络连接
const isOnline = ref(true)
onBeforeUnmount(() => {
  window.removeEventListener('online', () => isOnline.value = true)
  window.removeEventListener('offline', () => isOnline.value = false)
})

// 自动恢复上传
watch(isOnline, (newVal) => {
  if (newVal) {
    startAllUploads()
  }
})

onMounted(() => {
  isOnline.value = navigator.onLine
})

// 错误详情生成方法
const getErrorDetail = (fileItem) => {
  if (fileItem.errorMsg?.includes('Network Error')) {
    return '网络连接已中断，请检查网络后重试'
  }
  if (fileItem.errorMsg?.includes('timeout')) {
    return '上传超时，请检查网络稳定性'
  }
  if (fileItem.retries >= MAX_FILE_RETRIES) {
    return '上传失败（已达最大重试次数）'
  }
  return `上传失败（${fileItem.errorMsg || '未知错误'}）`
}

// 网络状态检测
const isConnectionLost = (fileItem) => {
  return fileItem.status === STATUS.UPLOADING &&
    !navigator.onLine &&
    Date.now() - fileItem.lastProgressTime > 5000
}



// 文件上传类
class UploadManager {
  constructor(file, isEncrypted = false) {
    this.file = file
    this.isEncrypted = isEncrypted
    this.chunkSize = this.calculateDynamicChunkSize(file.size);
    this.chunks = reactive([])
    this.fileHash = ''
    this.encryptionKey = null
    this.iv = null
    this.salt = null
    this.currentIV = null
    this.progress = reactive({
      percentage: 0,
      uploadedSize: 0
    })
    this.uploadedChunks = new Set()
    this.retryCounts = new Map()
    this.limiter = pLimit(CONCURRENCY)
    this.onProgress = null // 新增进度回调
  }

  async initialize() {
    this.fileHash = await this.calculateHash()
    this.loadLocalProgress()
    // 恢复salt和密钥
    const storedParams = localStorage.getItem(`encParams-${this.fileHash}`);
    if (storedParams) {
      const { salt } = JSON.parse(storedParams);
      this.salt = CryptoJS.enc.Hex.parse(salt);
      this.encryptionKey = this.deriveKey(this.salt);
    }

    try {
      const { data } = await axios.get(`${import.meta.env.VITE_APP_BASE_API}/minio/check`, {
        params: {
          fileHash: this.fileHash,
          filename: this.file.name
        },
        headers: {
          'Authorization': headers.value.Authorization,
        }
      })
      data.uploadedChunks?.forEach(chunk => this.uploadedChunks.add(chunk))

      if (this.isEncrypted) {
        if (!this.salt) {
          await this.generateEncryptionParams()
          await this.validateEncryption()
        }
      }
    } catch (error) {
      console.error('初始化失败:', error)
    }
  }

  calculateDynamicChunkSize(fileSize) {
    const sizeMB = fileSize / 1024 / 1024;
    if (sizeMB > 1024) return 20 * 1024 * 1024; // 大文件20MB分片
    if (sizeMB > 100) return 10 * 1024 * 1024;  // 中等文件10MB
    return 5 * 1024 * 1024;                     // 小文件5MB
  }

  async validateEncryption() {
    // 添加salt检查
    if (!this.salt || this.salt.words.length !== 4) { // 128-bit salt = 4 words
      throw new Error('Salt未正确生成');
    }
  }

  // generateEncryptionParams 方法中移除IV生成
  async generateEncryptionParams() {
    // 只生成salt和密钥
    this.salt = CryptoJS.lib.WordArray.random(128 / 8);
    this.encryptionKey = this.deriveKey(this.salt); // 密钥派生方法需独立
    //  保存密钥和salt
    localStorage.setItem(`encParams-${this.fileHash}`, JSON.stringify({
      salt: CryptoJS.enc.Hex.stringify(this.salt),
      encryptionKey: CryptoJS.enc.Hex.stringify(this.encryptionKey)
    }));
  }

  // 新增密钥派生方法
  deriveKey(salt) {
    return CryptoJS.PBKDF2(
      CryptoJS.enc.Utf8.parse("secure-passphrase-" + this.fileHash),
      salt,
      { keySize: 8, iterations: 100000, hasher: CryptoJS.algo.SHA256 } // AES-256
    );
  }

  async calculateHash() {
    return new Promise((resolve) => {
      const spark = new SparkMD5.ArrayBuffer();
      const reader = new FileReader();
      const chunkSize = 5 * 1024 * 1024;
      const samples = [];
      const SPARSE_SAMPLING_INTERVAL = this.file.size > 1_000_000_000 ? 20 : 10;

      // 稀疏采样（减少数据量）
      for (let offset = 0; offset < this.file.size; offset += chunkSize * SPARSE_SAMPLING_INTERVAL) {
        samples.push(offset);
      }

      // 强制采样关键区域
      samples.push(0); // 头部
      samples.push(Math.floor(this.file.size / 2)); // 中部
      samples.push(Math.max(0, this.file.size - chunkSize)); // 尾部

      // 去重并排序
      const uniqueSamples = [...new Set(samples)].sort((a, b) => a - b);

      let current = 0;
      const loadNext = () => {
        if (current >= uniqueSamples.length) {
          resolve(spark.end());
          return;
        }
        const start = uniqueSamples[current];
        const end = Math.min(start + chunkSize, this.file.size);
        const blob = this.file.slice(start, end);
        reader.readAsArrayBuffer(blob);
      };

      reader.onload = (e) => {
        spark.append(e.target.result);
        current++;
        loadNext();
      };

      loadNext();
    });
  }

  async startUpload() {
    // isUploading.value = true
    uploadStatus.value = '开始上传...'

    try {
      const generator = this.chunkGenerator()
      this.prepareChunks(generator)
      await this.uploadAllChunks()
      const result = await this.mergeFile()
      if (result === 200) {
        uploadStatus.value = '上传完成!'
        return true // 上传成功
      } else {
        uploadStatus.value = '上传失败!'
        return false // 上传失败
      }
    } catch (error) {
      uploadStatus.value = `上传失败: ${error.message}`
      console.error('上传失败:', error)
      throw error // 传播错误
    } finally {
      // isUploading.value = false
    }
  }

  *chunkGenerator() {
    let offset = 0
    while (offset < this.file.size) {
      const chunk = this.file.slice(offset, offset + this.chunkSize)
      yield { index: Math.floor(offset / this.chunkSize), chunk }
      offset += this.chunkSize
    }
  }

  prepareChunks(generator) {
    for (const { index, chunk } of generator) {
      if (!this.uploadedChunks.has(index)) {
        this.chunks.push({
          index,
          chunk,
          progress: 0,
          status: 'pending'
        })
      } else {
        // 标记已上传分片
        this.chunks.push({
          index,
          chunk,
          progress: 100,
          status: 'success'
        });
      }
    }
  }

  async uploadAllChunks() {
    const uploadTasks = this.chunks
      .filter(chunk => chunk.status !== 'success')
      .map(chunk => this.limiter(() =>
        this.uploadChunkWithRetry(chunk).finally(() => {
          // 每个分片完成时更新进度
          this.updateTotalProgress();
        })
      ));

    await Promise.all(uploadTasks)
  }

  async uploadChunkWithRetry(chunk) {
    for (let retry = 0; retry <= MAX_RETRY; retry++) {
      try {
        const response = await this.uploadChunk(chunk)
        if (response.data.code === 500) {
          throw new Error('上传失败')
        }
        return response
      } catch (error) {
        // 网络错误立即失败
        if (error.message.includes('Network Error')) {
          throw new Error('网络中断')
        }

        if (retry === MAX_RETRY) {
          throw new Error(`分片 ${chunk.index} 上传失败: ${error.message}`)
        }
        await new Promise(resolve =>
          setTimeout(resolve, 1000 * Math.pow(2, retry))
        )
      }
    }
  }

  async uploadChunk(chunk) {
    let processedChunk = chunk.chunk;

    const formData = new FormData();


    if (this.isEncrypted) {
      // 分块加密处理
      processedChunk = await this.encryptChunk(chunk);

      console.assert(
        processedChunk.size % 16 === 0,
        `无效的加密块大小: ${processedChunk.size} bytes`
      );
      // 首分片携带salt，后续分片不携带
      if (chunk.index === 0) {
        formData.append('salt', CryptoJS.enc.Hex.stringify(this.salt));
      }
    }

    formData.append('file', processedChunk);
    formData.append('hash', this.fileHash);
    formData.append('index', chunk.index.toString());

    return axios.post(`${import.meta.env.VITE_APP_BASE_API}/minio/upload`, formData, {
      headers: {
        ...headers.value,
        'X-Encrypted': this.isEncrypted.toString()
      },
      onUploadProgress: (progressEvent) => {
        // 分片级别进度更新
        const percent = Math.round(
          (progressEvent.loaded / progressEvent.total) * 100
        );
        chunk.progress = percent;

        // 立即触发总进度更新
        this.updateTotalProgress();
      }
    });
  }

  // Blob -> WordArray
  async blobToWordArray(blob) {
    try {
      const arrayBuffer = await blob.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);
      return CryptoJS.lib.WordArray.create(uint8Array);
    } catch (error) {
      console.error('Blob转换失败:', error);
      throw new Error('文件分片读取失败');
    }
  }

  // WordArray -> Blob
  wordArrayToBlob(wordArray) {
    try {
      // 1. 创建 Uint8Array 并填充数据
      const uint8Array = new Uint8Array(wordArray.sigBytes);
      let offset = 0;

      // 2. 遍历 WordArray 的每个 32 位字（4 字节）
      wordArray.words.forEach((word) => {
        uint8Array[offset++] = (word >> 24) & 0xff; // 最高有效字节
        uint8Array[offset++] = (word >> 16) & 0xff;
        uint8Array[offset++] = (word >> 8) & 0xff;
        uint8Array[offset++] = word & 0xff;         // 最低有效字节
      });

      // 3. 创建 Blob（截断到实际长度）
      return new Blob([uint8Array.slice(0, wordArray.sigBytes)], {
        type: 'application/octet-stream'
      });
    } finally {
      // 4. 强制释放内存（可选）
      wordArray.words = null;
      wordArray.sigBytes = 0;
    }
  }

  // UploadManager 类中修改 encryptChunk 方法
  async encryptChunk(chunk) {
    try {
      // 生成分片专属IV（基于文件哈希和分片索引）
      const ivSource = CryptoJS.SHA256(
        CryptoJS.enc.Utf8.parse(this.fileHash + chunk.index.toString())
      ).toString(); // SHA256哈希
      // console.log("[前端 IV 输入]", this.fileHash + chunk.index.toString()); // 记录输入字符串
      // console.log("[前端 IV 源]", ivSource.slice(0, 32)); // 记录生成的 IV 源
      const chunkIV = CryptoJS.enc.Hex.parse(ivSource.slice(0, 32)); // 取前128位作为IV

      // 转换Blob为WordArray
      const chunkData = await this.blobToWordArray(chunk.chunk);

      // 加密
      const encrypted = CryptoJS.AES.encrypt(
        chunkData,
        this.encryptionKey,
        {
          iv: chunkIV,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
        }
      );

      return this.wordArrayToBlob(encrypted.ciphertext);
    } catch (error) {
      console.error('分片加密失败:', error);
      throw error;
    }
  }

  // 在UploadManager类中添加
  updateTotalProgress() {
    const totalLoaded = this.chunks.reduce((sum, chunk) => {
      return sum + (chunk.progress / 100) * chunk.chunk.size;
    }, 0);

    const totalSize = this.file.size;
    const percentage = totalSize > 0
      ? Math.round((totalLoaded / totalSize) * 100)
      : 0;

    if (this.onProgress) {
      this.onProgress(Math.min(percentage, 99)); // 完成前不超过99%
    }
  }

  // 在mergeFile方法中添加加密参数处理
  async mergeFile() {
    const payload = {
      filename: this.file.name,
      fileHash: this.fileHash,
      totalChunks: Math.ceil(this.file.size / this.chunkSize),
      encrypted: this.isEncrypted,
      uploadedChunksCount: this.uploadedChunks.size
    };

    const response = await axios.post(
      `${import.meta.env.VITE_APP_BASE_API}/minio/merge`,
      payload,
      {
        headers: {
          'Authorization': headers.value.Authorization,
          'X-Encrypted': this.isEncrypted.toString()
        }
      }
    );
    if (response.data.code === 200) {
      const filePath = response.data.msg;
      this.createDocumentRecord(this.file, filePath, '')
      this.cleanLocalProgress
      return 200
    } else {
      console.error('合并文件失败，文档上传失败');
      return 500
    }
  }

  // 本地存储相关方法
  saveLocalProgress() {
    const progressData = {
      fileHash: this.fileHash,
      uploadedChunks: Array.from(this.uploadedChunks)
    }
    localStorage.setItem(this.fileHash, JSON.stringify(progressData))
  }

  loadLocalProgress() {
    const data = localStorage.getItem(this.fileHash)
    if (data) {
      const { uploadedChunks } = JSON.parse(data)
      this.uploadedChunks = new Set(uploadedChunks)
    }
  }

  cleanLocalProgress() {
    localStorage.removeItem(this.fileHash)
    localStorage.removeItem(`encParams-${this.fileHash}`)
  }

  /**
 * 创建文档记录
 * @param {File} rawFile - 原始文件对象
 * @param {string} filePath - 服务端返回的存储路径
 * @param {string} content - OCR 提取的内容
 */
  createDocumentRecord = async (rawFile, filePath, content) => {
    // 参数重命名以明确接收的是原始文件对象
    // 验证必要参数存在
    if (!rawFile || !rawFile.name) {
      throw new Error('无效的文件对象，缺少必要属性')
    }

    // 更可靠的文件类型获取方式
    const getFileType = () => {
      // 优先使用MIME类型判断
      const mimeType = rawFile.type.split('/').pop() // 例如：pdf从application/pdf中提取
      if (mimeType && mimeType !== 'octet-stream') {
        return mimeType.toLowerCase()
      }
      // 回退到文件扩展名
      const ext = rawFile.name.split('.').pop()
      return ext ? ext.toLowerCase() : 'unknown'
    }

    // 构造文档数据
    const docData = {
      archiveId: importArchiveId,
      name: rawFile.name, // 原始文件名
      fileType: getFileType(), // 改进的类型获取
      fileSize: Math.round(rawFile.size / 1024 * 10) / 10, // 更精确的四舍五入
      filePath: filePath,
      content: content || '',
      uploadTime: new Date().toISOString(),
      encryptFlag: isEncrypt.value ? 1 : 0 // 修正变量名拼写
    }

    // 参数验证
    if (!docData.filePath || docData.fileSize <= 0) {
      throw new Error('无效的文档参数: ' + JSON.stringify(docData))
    }

    try {
      console.log('[DEBUG] 创建文档记录请求:', docData)
      const response = await addDocument(docData)
      console.log('[DEBUG] 创建文档记录响应:', response)
      return response
    } catch (error) {
      console.error('[ERROR] 文档记录创建失败:', {
        requestData: docData,
        errorDetails: {
          message: error.message,
          stack: error.stack,
          response: error.response?.data
        }
      })
      throw new Error(`创建失败: ${error.message}`)
    }
  }
}
</script>

<style scoped>
.upload-container {
  max-width: 800px;
  margin: 2rem auto;
  padding: 1.5rem;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.upload-area {
  border: 2px dashed #ccc;
  border-radius: 8px;
  padding: 2rem;
  text-align: center;
  transition: border-color 0.3s ease;
  position: relative;
}

.upload-area:hover {
  border-color: #2196F3;
}

.file-input {
  opacity: 0;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  cursor: pointer;
}

.upload-prompt {
  color: #666;
}

.upload-prompt .material-icons {
  font-size: 3rem;
  color: #2196F3;
  margin-bottom: 1rem;
}

.hint {
  font-size: 0.9rem;
  color: #999;
  margin-top: 0.5rem;
}

.file-list {
  margin-top: 1.5rem;
}

.file-item {
  background: #f8f9fa;
  border-radius: 8px;
  margin-bottom: 1rem;
  padding: 1rem;
  transition: all 0.3s ease;
}

.file-item.uploading {
  background: #e3f2fd;
}

.file-item.success {
  background: #e8f5e9;
}

.file-item.error {
  background: #ffebee;
}

.file-info {
  display: flex;
  align-items: center;
  margin-bottom: 0.5rem;
}

.file-icon .material-icons {
  font-size: 2rem;
  margin-right: 1rem;
  color: #757575;
}

.file-meta {
  flex-grow: 1;
}

.filename {
  font-weight: 500;
  margin-bottom: 0.25rem;
}

.file-size {
  font-size: 0.85rem;
  color: #666;
}

.file-actions {
  display: flex;
  gap: 0.5rem;
}

.progress-container {
  margin-top: 0.5rem;
}

.progress-bar {
  height: 8px;
  background: #eee;
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: #2196F3;
  transition: width 0.3s ease;
}

.progress-info {
  display: flex;
  justify-content: space-between;
  margin-top: 0.5rem;
  font-size: 0.9rem;
}

.status-text {
  color: #666;
}

.percentage {
  font-weight: 500;
  color: #2196F3;
}

.global-controls {
  margin-top: 1.5rem;
  display: flex;
  gap: 1rem;
  justify-content: flex-end;
}

button {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  transition: all 0.2s ease;
}

.start-btn {
  background: #2196F3;
  color: white;
}

.start-btn:disabled {
  background: #90caf9;
  cursor: not-allowed;
}

.clear-btn {
  background: #f44336;
  color: white;
}

.clear-btn:disabled {
  background: #ffcdd2;
  cursor: not-allowed;
}

.retry-btn {
  background: #ff9800;
  color: white;
  font-size: 0.85rem;
  padding: 0.25rem 0.75rem;
}

.remove-btn {
  background: none;
  color: #666;
  padding: 0;
}

.file-list-enter-active,
.file-list-leave-active {
  transition: all 0.3s ease;
}

.file-list-enter-from,
.file-list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

/* 新增停滞状态样式 */
.status-stalled {
  background: #fff3e0;
  border-left: 4px solid #ffa726;
}

/* 错误状态样式 */
.status-error {
  border-left: 4px solid #ff5252;
  background: #ffebee;
}

.error-detail {
  color: #d32f2f;
  font-size: 0.85rem;
  margin-top: 0.5rem;
  line-height: 1.4;
}

/* 重试按钮优化 */
.retry-btn {
  background: #ff9800;
  padding: 0.4rem 0.8rem;
  font-size: 0.9rem;
}

.retry-btn:hover {
  background: #fb8c00;
}
</style>